#include <stdio.h>

#include <caml/alloc.h>
#include "octi_rawb.h"
#include <caml/custom.h>
#include <caml/fail.h>
#include <caml/intext.h>
#include <caml/memory.h>
#include <caml/mlvalues.h>

#define OCTI_RAWB_MAX_MEMORY 256*1024*1024
/* Cf. Bigarray
   256 Mb -- after allocating that much, it's probably worth speeding
   up the major GC */

/* Helper function to allocate a record of two double floats, Cf. Bigarray */

static value copy_two_doubles (double d0, double d1)
{
	CAMLparam0 ();
	CAMLlocal1 (v_res);
	v_res = alloc_small(2 * Double_wosize, Double_array_tag);
	Store_double_field(v_res, 0, d0);
	Store_double_field(v_res, 1, d1);
	CAMLreturn (v_res);
}

/* Operation table */

static void octi_rawb_bytes_finalize (value v);
static int octi_rawb_bytes_compare (value v1, value v2);
static intnat octi_rawb_bytes_hash (value v);
static struct custom_operations octi_rawb_bytes_ops = {
	"octi_rawb_bytes",
	octi_rawb_bytes_finalize,
	octi_rawb_bytes_compare,
	octi_rawb_bytes_hash,
	custom_serialize_default,
	custom_deserialize_default
};

/* Initialization */

CAMLprim value octi_rawb_init (value v_unit)
{
	CAMLparam1 (v_unit);
	register_custom_operations (&octi_rawb_bytes_ops);
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_create (value v_size)
{
	CAMLparam1 (v_size);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytebuffer * bb;
	struct octi_rawb_bytes * b;
	intnat size = Long_val (v_size);
	void * bytes;

	//Assert (size > 0);
	bytes = caml_stat_alloc (size);
	bb = caml_stat_alloc (sizeof (struct octi_rawb_bytebuffer));
	bb -> bytes = bytes;
	bb -> size = size;
	bb -> refcount = 1;
	v_res = alloc_custom (&octi_rawb_bytes_ops,
		  	      sizeof (struct octi_rawb_bytes),
			      size, OCTI_RAWB_MAX_MEMORY);
	b = Octi_rawb_bytes_val (v_res);
	b->bytes = bb->bytes;
	b->bytebuffer = bb;
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_make_offset (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	struct octi_rawb_bytebuffer * bb = b->bytebuffer;
	struct octi_rawb_bytes * newb;
	
	// Assert (offset >= 0);
	// Assert (offset + b->bytes < bb->bytes + bb->size);
	v_res = alloc_custom (&octi_rawb_bytes_ops,
			      sizeof (struct octi_rawb_bytes),
			      0,1);
	newb = Octi_rawb_bytes_val (v_res);
	newb->bytes = b->bytes + offset;
	newb->bytebuffer = bb;
	bb->refcount++;
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_get_bbsize (value v_bytes)
{
	CAMLparam1 (v_bytes);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	struct octi_rawb_bytebuffer * bb = b->bytebuffer;
	v_res = Val_long (bb->size);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_get_bbrefcount (value v_bytes)
{
	CAMLparam1 (v_bytes);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	v_res = Val_long (b->bytebuffer->refcount);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_get_offset (value v_bytes)
{
	CAMLparam1 (v_bytes);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	v_res = Val_long (b->bytes - b->bytebuffer->bytes);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_get_bbaddress (value v_bytes)
{
	CAMLparam1 (v_bytes);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	v_res = Val_long ((intnat) b->bytebuffer->bytes);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_get_address (value v_bytes)
{
	CAMLparam1 (v_bytes);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	v_res = Val_long ((intnat) b->bytes);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_info (value v_variant)
{
	CAMLparam1 (v_variant);
	CAMLlocal1 (v_res);
	if (v_variant == caml_hash_variant("Info_sizeof_int")) {
	    v_res = Val_long ((intnat) sizeof (int));}
	else if  (v_variant == caml_hash_variant("Info_sizeof_long")) {
	    v_res = Val_long ((intnat) sizeof (long));}
	else if  (v_variant == caml_hash_variant("Info_sizeof_intnat")) {
	    v_res = Val_long ((intnat) sizeof (intnat));}
	else if  (v_variant == caml_hash_variant("Info_sizeof_value")) {
	    v_res = Val_long ((intnat) sizeof (value));}
	else if  (v_variant == caml_hash_variant("Info_Max_long")) {
	    v_res = Val_long (Max_long);}
	else if  (v_variant == caml_hash_variant("Info_Min_long")) {
	    v_res = Val_long (Min_long);}
	else {caml_invalid_argument ("info type unknown");}
	CAMLreturn (v_res);
}

/* Finalization of a bytes structure */

static void octi_rawb_bytes_finalize (value v_bytes)
{
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	struct octi_rawb_bytebuffer * bb = b->bytebuffer;
	
	if (-- bb->refcount == 0) {
		free (bb->bytes);
		stat_free (bb);
	}
}

static int octi_rawb_bytes_compare (value v1, value v2)
{
	CAMLparam2 (v1, v2);
	struct octi_rawb_bytes * b1 = Octi_rawb_bytes_val (v1);
	struct octi_rawb_bytes * b2 = Octi_rawb_bytes_val (v2);

	CAMLreturnT (int, ((int) (b2->bytes - b1->bytes)));
}

static intnat octi_rawb_bytes_hash (value v)
{
	CAMLparam1 (v);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v);
	struct octi_rawb_bytebuffer * bb = b->bytebuffer;
	int n = bb->size + (bb->bytes - b->bytes);
	int i;
#define COMBINE(h,v) ((h << 4) + h + (v))
	unsigned char * p = b->bytes;
	intnat h = 0;
	for (i = 0; i < n; i++) { 
		h = COMBINE(h, *p++);
	}
#undef COMBINE
	CAMLreturnT (intnat, h);
}

CAMLprim value octi_rawb_bytes_c_char_set (value v_bytes, value v_offset, value v_x)
{
	CAMLparam3 (v_bytes, v_offset, v_x);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	// Assert b->data is aligned
	* (((char *) (b->bytes)) + Long_val (v_offset)) = (char) (Int_val (v_x));
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_c_char_get (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	int res;
	// Assert b->data is aligned
	// Assert offset ...
	res = * (((unsigned char *) (b->bytes)) + offset);
	v_res = Val_int ((int) res);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_c_nativeint_set (value v_bytes, value v_offset, value v_x)
{
	CAMLparam3 (v_bytes, v_offset, v_x);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	// Assert b->data is aligned
	* (((intnat *) (b->bytes)) + Long_val (v_offset)) = Nativeint_val (v_x);
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_c_nativeint_get (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	intnat res;
	// Assert b->data is aligned
	// Assert offset ...
	res = * (((intnat *) (b->bytes)) + offset);
	v_res = caml_copy_nativeint (res);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_c_float64_set (value v_bytes, value v_offset, value v_x)
{
	CAMLparam3 (v_bytes, v_offset, v_x);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	// Assert b->data is aligned
	* (((double *) (b->bytes)) + Long_val (v_offset)) = Double_val (v_x);
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_c_float64_get (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	// Assert b->data is aligned
	// Assert offset ...
	v_res = caml_copy_double (* (((double *) (b->bytes)) + offset));
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_c_complexfloat64_set (value v_bytes, value v_offset, value v_z)
{
	CAMLparam3 (v_bytes, v_offset, v_z);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	// Assert b->data is aligned
	// Assert offset ...
	double * p = ((double *) b->bytes) + offset * 2;
	p[0] = Double_field (v_z, 0);
	p[1] = Double_field (v_z, 1);
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_c_complexfloat64_get (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	// Assert b->data is aligned
	// Assert offset ...
	double * p = ((double *) b->bytes) + offset * 2;
	v_res = copy_two_doubles (p[0], p[1]);
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_c_float32_set (value v_bytes, value v_offset, value v_x)
{
	CAMLparam3 (v_bytes, v_offset, v_x);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	// Assert b->data is aligned
	* (((float *) (b->bytes)) + Long_val (v_offset)) = (float) Double_val (v_x);
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_c_float32_get (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	// Assert b->data is aligned
	// Assert offset ...
	v_res = caml_copy_double ((float) (* (((float *) (b->bytes)) + offset)));
	CAMLreturn (v_res);
}

CAMLprim value octi_rawb_bytes_c_complexfloat32_set (value v_bytes, value v_offset, value v_z)
{
	CAMLparam3 (v_bytes, v_offset, v_z);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	// Assert b->data is aligned
	// Assert offset ...
	float * p = ((float *) b->bytes) + offset * 2;
	p[0] = (float) Double_field (v_z, 0);
	p[1] = (float) Double_field (v_z, 1);
	CAMLreturn (Val_unit);
}

CAMLprim value octi_rawb_bytes_c_complexfloat32_get (value v_bytes, value v_offset)
{
	CAMLparam2 (v_bytes, v_offset);
	CAMLlocal1 (v_res);
	struct octi_rawb_bytes * b = Octi_rawb_bytes_val (v_bytes);
	intnat offset = Long_val (v_offset);
	// Assert b->data is aligned
	// Assert offset ...
	float * p = ((float *) b->bytes) + offset * 2;
	v_res = copy_two_doubles ((double) p[0], (double) p[1]);
	CAMLreturn (v_res);
}
